#include "stdafx.h"
#include "Stimulus.h"

namespace SpikingNeuronLib
{
	// Public Members -------------------------------------------------------------------------------------------

	Stimulus::Stimulus(int patternStimulationsPerSecond, String^ firingEvents)
	{
		SpikingNeuronLib::Pattern^ pattern = gcnew SpikingNeuronLib::Pattern(firingEvents);
		List<FiringEvent>^ oneSecondEventList = Stimulus::GetEventsFromRepeatedPattern(pattern, patternStimulationsPerSecond);

		this->Init(pattern, patternStimulationsPerSecond, oneSecondEventList);
	}

	Stimulus::Stimulus(int patternStimulationsPerSecond, System::Collections::Generic::IEnumerable<FiringEvent>^ firingEvents)
	{
		SpikingNeuronLib::Pattern^ pattern = gcnew SpikingNeuronLib::Pattern(firingEvents);
		List<FiringEvent>^ oneSecondEventList = Stimulus::GetEventsFromRepeatedPattern(pattern, patternStimulationsPerSecond);

		this->Init(pattern, patternStimulationsPerSecond, oneSecondEventList);
	}

	Stimulus::Stimulus(int patternStimulationsPerSecond, System::Collections::Generic::IEnumerable<Tuple<int, int>^>^ firingEvents)
	{
		SpikingNeuronLib::Pattern^ pattern = gcnew SpikingNeuronLib::Pattern(firingEvents);
		List<FiringEvent>^ oneSecondEventList = Stimulus::GetEventsFromRepeatedPattern(pattern, patternStimulationsPerSecond);

		this->Init(pattern, patternStimulationsPerSecond, oneSecondEventList);
	}

	Stimulus::Stimulus(int patternStimulationsPerSecond, array<FiringEvent>^ firingArray)
	{
		SpikingNeuronLib::Pattern^ pattern = gcnew SpikingNeuronLib::Pattern(firingArray);
		List<FiringEvent>^ oneSecondEventList = Stimulus::GetEventsFromRepeatedPattern(pattern, patternStimulationsPerSecond);

		this->Init(pattern, patternStimulationsPerSecond, oneSecondEventList);
	}

	Stimulus::Stimulus(int patternStimulationsPerSecond, List<FiringEvent>^ firingsList)
	{
		SpikingNeuronLib::Pattern^ pattern = gcnew SpikingNeuronLib::Pattern(firingsList);
		List<FiringEvent>^ oneSecondEventList = Stimulus::GetEventsFromRepeatedPattern(pattern, patternStimulationsPerSecond);

		this->Init(pattern, patternStimulationsPerSecond, oneSecondEventList);
	}

	Stimulus::Stimulus(int patternStimulationsPerSecond, List<FiringEvent>^ firingsList, int uniqueID)
	{
		SpikingNeuronLib::Pattern^ pattern = gcnew SpikingNeuronLib::Pattern(firingsList, uniqueID);
		List<FiringEvent>^ oneSecondEventList = Stimulus::GetEventsFromRepeatedPattern(pattern, patternStimulationsPerSecond);

		this->Init(pattern, patternStimulationsPerSecond, oneSecondEventList);
	}

	// Create constructors -------------------------------------------------------------------------------------------

    /// Create a new stimulus by allocating the specified input period (in milliseconds) to each repetition of the pattern
    Stimulus^ Stimulus::Create(SpikingNeuronLib::Pattern^ pattern, int inputPeriod)
	{
        System::Diagnostics::Debug::Assert(inputPeriod > 0 && inputPeriod <= 1000);
        int patternStimulationsPerSecond = (int) 1000.0 / inputPeriod;

        return gcnew Stimulus(patternStimulationsPerSecond, pattern->EventList);
	}

    /// Create a new stimulus by repeating the specified pattern the specified number of times each second
    Stimulus^ Stimulus::Create(int patternStimulationsPerSecond, SpikingNeuronLib::Pattern^ pattern)
	{
        System::Diagnostics::Debug::Assert(patternStimulationsPerSecond > 0 && patternStimulationsPerSecond <= 1000);

        return gcnew Stimulus(patternStimulationsPerSecond, pattern->EventList);
	}

    /// Create a new stimulus by allocating the specified input period (in milliseconds)
    /// to each repetition of the specified array of firing events
    Stimulus^ Stimulus::Create(array<FiringEvent>^ firingEvents, int inputPeriod)
	{
        System::Diagnostics::Debug::Assert(inputPeriod > 0 && inputPeriod <= 1000);
        int patternStimulationsPerSecond = (int) 1000.0 / inputPeriod;

        return gcnew Stimulus(patternStimulationsPerSecond, firingEvents);
	}

    /// Create a new stimulus by repeating the specified array of firing events the specified number of times each second
    Stimulus^ Stimulus::Create(int patternStimulationsPerSecond, array<FiringEvent>^ firingEvents)
	{
        System::Diagnostics::Debug::Assert(patternStimulationsPerSecond > 0 && patternStimulationsPerSecond <= 1000);

        return gcnew Stimulus(patternStimulationsPerSecond, firingEvents);
	}

    /// Create a new stimulus by allocating the specified input period (in milliseconds)
    /// to each repetition of the specified list of firing events
    Stimulus^ Stimulus::Create(List<FiringEvent>^ firingEvents, int inputPeriod)
	{
        System::Diagnostics::Debug::Assert(inputPeriod > 0 && inputPeriod <= 1000);
        int patternStimulationsPerSecond = (int) 1000.0 / inputPeriod;

        return gcnew Stimulus(patternStimulationsPerSecond, firingEvents);
	}

    /// Create a new stimulus by repeating the specified list of firing events the specified number of times each second
    Stimulus^ Stimulus::Create(int patternStimulationsPerSecond, List<FiringEvent>^ firingEvents)
	{
		System::Diagnostics::Debug::Assert(patternStimulationsPerSecond > 0 && patternStimulationsPerSecond <= 1000);

        return gcnew Stimulus(patternStimulationsPerSecond, firingEvents);
	}

	// Private Members -------------------------------------------------------------------------------------------

	void Stimulus::Init(SpikingNeuronLib::Pattern^ pattern, int patternStimulationsPerSecond, List<FiringEvent>^ oneSecondEventList)
	{
		_isEmpty = true;
		_patternStimulationsPerSecond = patternStimulationsPerSecond;
		_pattern = pattern;

		// create a dictionary of fired neurons for each millisecond
		// i.e. a list of neurons that fire for each millisecond key
		Dictionary<int, List<int>^>^ patternDictionary = gcnew Dictionary<int, List<int>^>(oneSecondEventList->Count);

		for (int i = 0; i < oneSecondEventList->Count; i++)
		{
			List<int>^ firedNeurons;
			int neuron = oneSecondEventList[i].NeuronIndex;
			int time = oneSecondEventList[i].Time;

			if (patternDictionary->ContainsKey(time))
			{
				firedNeurons = patternDictionary[time];
			}
			else
			{
				firedNeurons = gcnew List<int>();
				patternDictionary[time] = firedNeurons;
			}

			firedNeurons->Add(neuron);
		}

		_neuronFiringData = gcnew array<array<int>^>(1000);	// a slot for each msec
		for (int i = 0; i <	1000; i++)
		{
			_neuronFiringData[i] = nullptr;
		}

		if (patternDictionary->Count > 0)
		{
			// transfer the firing data to an array of arrays
			for each (KeyValuePair<int, List<int>^> kvp in patternDictionary)
			{
				int index = kvp.Key;
				if (index >= 1000)
				{
					// a firing event time has overflowed one second
					String^ message = String::Format("Stimulus pattern overflow at t={0}: firing event times in the source pattern are limited to < 1000 milliseconds", index);
					throw gcnew IndexOutOfRangeException(message);
				}
				else
				{
					List<int>^ firedNeuronsList = kvp.Value;
					if (firedNeuronsList->Count > 0)
					{
						_neuronFiringData[index] = firedNeuronsList->ToArray();
					}
				}
			}

			_isEmpty = false;
		}
	}

    List<FiringEvent>^ Stimulus::GetEventsFromRepeatedPattern(List<FiringEvent>^ firingsList, int patternStimulationsPerSecond)
	{
		// create a new event list by repeating the pattern with the specified number of times per second
		int totalEvents = patternStimulationsPerSecond * firingsList->Count;
		List<FiringEvent>^ eventList = gcnew List<FiringEvent>(totalEvents);

		if (patternStimulationsPerSecond > 0 && firingsList->Count > 0)
		{
			int inputPeriod = 1000 / patternStimulationsPerSecond;
			for each (FiringEvent event in firingsList)
			{
				int time = event.Time;
				int neuron = event.NeuronIndex;
				for (int i = 0; i < 1000; i += inputPeriod)
				{
					eventList->Add(FiringEvent(i + time, neuron, EventLabel::Unspecified));
				}
			}
		}

		return eventList;
	}

	List<FiringEvent>^ Stimulus::GetEventsFromRepeatedPattern(SpikingNeuronLib::Pattern^ pattern, int patternStimulationsPerSecond)
	{
		List<FiringEvent>^ firingsList = pattern->EventList;

        return Stimulus::GetEventsFromRepeatedPattern(firingsList, patternStimulationsPerSecond);
	}

	List<FiringEvent>^ Stimulus::GetEventsFromRepeatedPattern(System::Collections::Generic::IEnumerable<FiringEvent>^ firingEvents, int patternStimulationsPerSecond)
	{
		List<FiringEvent>^ firingsList = gcnew List<FiringEvent>(firingEvents);

		return Stimulus::GetEventsFromRepeatedPattern(firingsList, patternStimulationsPerSecond);
	}
}
